import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from numpy.core.function_base import linspace

def Get_Proper_Ticks(min, max, N=5):
    diff = (max-min) / N
    magnitude = 1
    while diff > 7.5:
        diff /= 10
        magnitude *= 10
    while diff <= 0.75:
        diff *= 10
        magnitude /= 10
    if diff < 1.5:
        diff = 1
    elif diff < 3.5:
        diff = 2
    else:
        diff = 5
    step = magnitude * diff
    tickmin, tickmax = 0, 0
    while tickmin >= min:
        tickmin -= step
    while tickmax <= max:
        tickmax += step
    ans = np.linspace(tickmin, tickmax, int((tickmax-tickmin)/step+1.5))
    mask = [ans-np.floor(ans) != 0]
    if len(ans[mask]) == 0:
        ans = np.array(ans, dtype=np.int32)
    else:
        ans = np.floor(ans*10+0.5)/10
    return ans

class SinglePlot:
    _xlabelmove, _ylabelmove = [0, 0], [0, 0]
    def __init__(self, labels=None, ticks=None, lims=None):
        params = {"font.family":'serif', "font.size": 20, "mathtext.fontset":'stix', "font.serif": ['SimSun'],}
        plt.rcParams.update(params)
        self._fig, self._ax = plt.subplots()
        self._fig.subplots_adjust(left=0, bottom=0, right=0.9, top=0.9)
        self._ax.spines[:].set_visible(False)
        self._ax.spines['bottom'].set_position(('data',0))
        self._ax.spines['left'].set_position(('data',0))
        self._ax.yaxis.set_ticks_position('left')
        self._labels = ['x', 'y'] if labels is None else labels
        self._ticks = ticks
        self._lims = lims
        plt.xticks(fontfamily="Times New Roman")
        plt.yticks(fontfamily="Times New Roman")
        self._xmin, self._xmax = 1e12, -1e12
        self._ymin, self._ymax = 1e12, -1e12

    def show(self):
        xlim = [self._xmin-0.1*(self._xmax-self._xmin), self._xmax+0.1*(self._xmax-self._xmin)] if self._lims is None else self._lims[0]
        ylim = [self._ymin-0.2*(self._ymax-self._ymin), self._ymax+0.1*(self._ymax-self._ymin)] if self._lims is None else self._lims[1]
        xrate, yrate = xlim[1]-xlim[0], ylim[1]-ylim[0]
        if self._ticks is None:
            self._xticks = Get_Proper_Ticks(self._xmin, self._xmax)
            self._yticks = Get_Proper_Ticks(self._ymin, self._ymax)
        else:
            self._xticks = Get_Proper_Ticks(self._xmin, self._xmax) if len(self._ticks[0])==0 else self._ticks[0]
            self._yticks = Get_Proper_Ticks(self._ymin, self._ymax) if len(self._ticks[1])==0 else self._ticks[1]
        xticklabels = [str(n) if n!=0 else '' for n in self._xticks]
        yticklabels = [str(n) if n!=0 else '' for n in self._yticks]
        axesarrow = [-xlim[0]/(xrate), -ylim[0]/(yrate)]
        arrow = mpatches.ArrowStyle.CurveFilledB(head_length=0.8, head_width=0.4)
        arrowproperty = dict(facecolor='black', arrowstyle=arrow)
        self._ax.set_xticks(self._xticks)
        self._ax.set_yticks(self._yticks)
        self._ax.set_xlim(xlim)
        self._ax.set_ylim(ylim)
        self._ax.set_xticklabels(xticklabels)
        self._ax.set_yticklabels(yticklabels)
        self._ax.text(xlim[1]-xrate*0.05+self._xlabelmove[0], -yrate*0.1+self._xlabelmove[1], self._labels[0])
        self._ax.text(-xrate*0.03+self._ylabelmove[0], ylim[1]+yrate*0.01+self._ylabelmove[1], self._labels[1])
        self._ax.text(xrate*0.02, -yrate*0.067, r'0', fontfamily="Times New Roman")
        self._ax.annotate("", xy=(1, axesarrow[1]), xytext=(0, axesarrow[1]), xycoords='axes fraction', arrowprops=arrowproperty)
        self._ax.annotate("", xy=(axesarrow[0], 1), xytext=(axesarrow[0], 0), xycoords='axes fraction', arrowprops=arrowproperty)
        plt.show()

    def plot(self, x, y, color=None, linewidth=None, linestyle=None):
        x = np.array(x) if type(x) != np.ndarray else x
        y = np.array(y) if type(y) != np.ndarray else y
        self._xmin, self._xmax = min(self._xmin, x.min()), max(self._xmax, x.max())
        self._ymin, self._ymax = min(self._ymin, y.min()), max(self._ymax, y.max())
        self._ax.plot(x, y, color=color, linewidth=linewidth, linestyle=linestyle)
    
    def set_xlabelmove(self, x, y):
        self._xlabelmove = [x, y]
    def set_ylabelmove(self, x, y):
        self._ylabelmove = [x, y]


if __name__ == '__main__':
    x = np.linspace(0, 3.14, 100)
    y = np.sin(x)
    fig1 = SinglePlot()
    fig1.plot(x, y)
    fig1.show()
